using System.Collections.Concurrent;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using NalogGarLoader.Data.Tables.GarEntries;

namespace NalogGarLoader.Model;

public class GarXmlFileParser
{
    private const string XmlSchemaStr = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
    private static ConcurrentDictionary<string, XmlSerializer> XmlSerializerCache { get; } = new();

    protected static XmlReaderSettings XmlReaderSettings { get; }

    protected Stream Stream { get; }
    protected Type ParseType { get; }
    protected XmlRootAttribute XmlRootAttribute { get; }
    static GarXmlFileParser()
    {
        GarXmlFileParser.XmlReaderSettings = new XmlReaderSettings()
        {
            IgnoreComments = true,
            IgnoreWhitespace = true
        };
    }

    public GarXmlFileParser(Stream stream, Type parseType)
    {
        this.Stream = stream;
        this.ParseType = parseType;

        var xmlRootAttribute = this.ParseType.GetCustomAttribute<XmlRootAttribute>();

        if (!parseType.IsSubclassOf(typeof(GarEntryBase)))
            throw new ArgumentException($"Тип {parseType.FullName} должен быть унаследован от {typeof(GarEntryBase).FullName}", nameof(parseType));

        if (xmlRootAttribute == null)
            throw new ArgumentException($"Тип {parseType.FullName} должен иметь атрибут {typeof(XmlRootAttribute).FullName}", nameof(parseType));

        this.XmlRootAttribute = xmlRootAttribute;
    }

    public IEnumerable<GarEntryBase> Parse()
    {
        if (!XmlSerializerCache.TryGetValue(this.ParseType.FullName!, out XmlSerializer? xs))
        {
            xs = new XmlSerializer(this.ParseType, this.XmlRootAttribute);
            XmlSerializerCache.TryAdd(this.ParseType.FullName!, xs);
        }

        using XmlReader xmlReader = XmlReader.Create(this.Stream, GarXmlFileParser.XmlReaderSettings);
        xmlReader.ReadToDescendant(this.XmlRootAttribute.ElementName);

        while (!xmlReader.EOF && xmlReader.NodeType == XmlNodeType.Element)
        {
            string nodeStr = GarXmlFileParser.XmlSchemaStr + xmlReader.ReadOuterXml();

            GarEntryBase? garEntry = xs.Deserialize(new StringReader(nodeStr)) as GarEntryBase;

            if (garEntry == null)
                throw new InvalidOperationException($"Ошибка разбора узла {this.XmlRootAttribute.ElementName}.\nXml: {nodeStr}");

            yield return garEntry;
        }
    }
}